/*
 * Created on 2006-3-15
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package auditoryUIbk;
import java.awt.Point;
import java.awt.Rectangle;
//import java.awt.Polygon;
import java.util.*;
import java.io.*;
import java.text.SimpleDateFormat;
import java.text.ParseException;


public class Gesture extends DefaultGestureObject
  implements Serializable, Cloneable {
  // todo: probably want to implement the AWT Shape inteface when it's
  // finalized in a later release (> 1.2) (may want to subclass
  // java.awt.geom.GeneralPath)
  /** Throw out pixels closer together than the square root of this
   * distance (in pixels). */
  public static final String CREATION_DATE_PROP = "creation date";
  public static final String POINTS_PROP = "points";
  static final int filterThreshold = 4*4;
  static final long serialVersionUID = -783808682935248803L;
  /** Init lastx, lasty so the first point will be sure to be far
    * enough away from them not to be filtered out. */
  protected int lastx = Integer.MIN_VALUE, lasty = Integer.MIN_VALUE;
  // properties

  protected TimedPolygon points;
  protected ArrayList pressureValues;
  
  protected boolean normalized = false;
  protected boolean enabled;
  protected Date creationDate;
  
  protected int miSize;
  
  public Gesture(int size)
  {
    this(null);
    miSize = size;
  }

  public Gesture(Gesture g)
  {
    super();

    if (g == null) {
      points = new TimedPolygon();
    }
    else {
      points = new TimedPolygon(g.points);
    }
    enabled = true;
    creationDate = new Date(System.currentTimeMillis());
    pressureValues = new ArrayList();
  }
  
  public void addPoint(int x, int y){
  	addPoint(x, y, 0);
  }
  
  /**
   * Filters out points that are too close together
   */
  public void addPoint(int x, int y, int z)
  {
    int dx = lastx - x;
    int dy = lasty - y;
    int distSq = dx*dx + dy*dy;
    if (distSq > filterThreshold) {
      points.addPoint(x, y);
      pressureValues.add(new Integer(z));
      lastx = x;
      lasty = y;
      if ((x < 0) || (y < 0)) {
	normalized = false;
      }
      propChangeSupport.firePropertyChange(POINTS_PROP,
					   null, new Point(x, y));
      setChanged();
      notifyObservers(new Point(x, y));
    }
  }
  
  /**
   * Return a copy of the points of the polygon
   * (copy instead of ref so points can't be changed).  To change
   * the points, use setPoints (or addPoint).
   */
  public TimedPolygon getPoints()
  {
    return new TimedPolygon(points);
  }
  
  public ArrayList getPressureValues(){

  	return pressureValues;
  }

  /**
   * This does not change the creation date.
   */
  public void setPoints(TimedPolygon p)
  {
    //System.out.println("Setting points for " + this + " to " + p);
    if (p != points) {
      TimedPolygon oldValue = points;
      points = p;
      // it might really be normalized, but it's not worth checking
      normalized = false;
      if (points.npoints > 0) {
	lastx = points.xpoints[points.npoints-1];
	lasty = points.ypoints[points.npoints-1];
      }
      else {
	lastx = Integer.MIN_VALUE;
	lasty = Integer.MIN_VALUE;
      }
      propChangeSupport.firePropertyChange(POINTS_PROP, oldValue, points);
      setChanged();
      notifyObservers(points);
    }
  }

  public void clearPoints()
  {
    TimedPolygon oldValue = points;
    setPoints(new TimedPolygon());
    propChangeSupport.firePropertyChange(POINTS_PROP, oldValue, points);
    pressureValues.clear();
  }
  
  public void analysePoints(){
  	//size is the size of the screen
  	//we treat different screen size differently
  	points.calculateAngles();
  	points.calculateTurningPoints(miSize);
  }
  
  public ArrayList getTurningPoints(){
  	return points.getTurningPoints();
  }

  public Rectangle getBounds()
  {
    return points.getBounds();
  }

  public boolean isEnabled()
  {
    return enabled;
  }

  public void setEnabled(Boolean e)
  {
    setEnabled(e.booleanValue());
  }
  
  public void setEnabled(boolean e)
  {
    if (e != enabled) {
      enabled = e;
      propChangeSupport.firePropertyChange(ENABLED_PROP, !enabled, enabled);
      setChanged();
      notifyObservers(new Boolean(enabled));
    }
  }
  
  /**
   * Move the corner of the gesture to the origin
   */
  public void normalize()
  {
    if (!normalized) {
      TimedPolygon oldPoints = points;
      normalized = true;
      Rectangle bounds = points.getBounds();
      points.translate(-bounds.x, -bounds.y);
      propChangeSupport.firePropertyChange(POINTS_PROP, oldPoints, points);
      setChanged();
      notifyObservers(points);
    }
  }

  public int size()
  {
    return points.npoints;
  }

  public boolean hasProperty(String name)
  {
    if ((name == CREATION_DATE_PROP) || (name == POINTS_PROP) ||
	(name == ENABLED_PROP)) {
      return true;
    }
    else {
      return super.hasProperty(name);
    }
  }

  public void setProperty(String name, Object value)
  {
    if (name == CREATION_DATE_PROP) {
      setCreationDate((Date) value);
    }
    else if (name == POINTS_PROP) {
      setPoints((TimedPolygon) value);
    }
    else if (name == ENABLED_PROP) {
      setEnabled(((Boolean) value).booleanValue());
    }
    else {
      super.setProperty(name, value);
    }
  }

  public Object getProperty(String name)
  {
    if (name == CREATION_DATE_PROP) {
      return getCreationDate();
    }
    else if (name == POINTS_PROP) {
      return getPoints();
    }
    else if (name == ENABLED_PROP) {
      return new Boolean(isEnabled());
    }
    else {
      return super.getProperty(name);
    }
  }

  /** For debugging.  Print out all points of the Gesture. */
  public void dump(PrintStream out)
  {
    for (int i = 0; i < points.npoints; i++) {
      out.print(points.xpoints[i] + ", " + points.ypoints[i] + " ");
    }
    out.println();
  }

  public void printTiming(PrintStream out)
  {
    final int ncols = 4;

    for (int i = 0; i < points.npoints; i++) {
      out.print("" + points.times[i]);
      if ((i % ncols) == (ncols-1)) {
	out.println();
      }
      else {
	out.print("\t");
      }
    }
    out.println();
  }
  
  /**
   * Creation date is set automatically, so normally this should not need
   * to be called from other classes.
   */
  public void setCreationDate(Date d)
  {
    if (creationDate != d) {
      Date oldValue = creationDate;
      creationDate = d;
      propChangeSupport.firePropertyChange(CREATION_DATE_PROP,
					   oldValue, creationDate);
      setChanged();
      notifyObservers(creationDate);
    }
  }

  public Date getCreationDate()
  {
    return creationDate;
  }

  // I/O

  public void write(Writer writer) throws IOException
  {
	if (author != null) {
	  writer.write("author\t" + author + "\n");
	}
	writer.write("normalized\t" + normalized + "\n");
	if (creationDate != null) {
	  SimpleDateFormat dateFormat = new SimpleDateFormat();
	  writer.write("creationDate\t" + dateFormat.format(creationDate) +
		   "\n");
	}
	writer.write("points\t" + points.npoints + "\n");
	for (int i = 0; i < points.npoints; i++) {
	  writer.write("\t" + points.xpoints[i] + "\t" + points.ypoints[i] +
		   "\t" + points.times[i] + "\n");
	}
	writer.write("endgesture\n");
  }
  
  public static Gesture read(Reader reader) throws IOException,
	ParseException
  {
	return read(new TokenReader(reader));
  }
  
  public static Gesture read(TokenReader r) throws IOException,
	ParseException
  {
  	//TODO: need to work on this one so that the size is read from the file
  	//currently, we use a temporary hack to gesture as 0
	Gesture result = new Gesture(0);
    
	boolean done = false;

	while (!done) {
	  String token;
	  try {
	token = r.readToken().intern();
	if (token == "author") {
	  result.author = r.readLine();
	  result.author.trim();
	}
	else if (token == "normalized") {
	  result.normalized = r.readBoolean();
	}
	else if (token == "creationDate") {
	  SimpleDateFormat dateFormat = new SimpleDateFormat();
	  result.creationDate = dateFormat.parse(r.readLine());
	}
	else if (token == "points") {
	  try {
		int numPoints = r.readInt();
		TimedPolygon poly = new TimedPolygon();
	    
		for (int i = 0; i < numPoints; i++) {
		  int x, y;
		  long t;
		  x = r.readInt();
		  y = r.readInt();
		  t = r.readLong();
		  poly.addPoint(x, y, t);
		}
		result.points = poly;
	  }
	  catch (NumberFormatException e) {
		throw new IOException("bad argument for points (" + e + ")");
	  }
	}
	else if (token == "endgesture") {
	  done = true;
	}
	  }
	  catch (EOFException e) {
	done = true;
	  }
	}
	//System.err.println("Read gesture with " + result.getPoints().npoints +
	//" points");
	return result;
  }
 

  public String toString()
  {
    return super.toString() + "/" + points.toString();
  }
}